﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace Compiler
{
    public class LR0Builder
    {
        private Grammar grammar;

        private Graph<HashSet<LR0Item>, Symbol> graph;
        private HashSet<LR0Item> StartSet;
        private LR0Item EndItem;
        private Builder.BuildMethod buildMethod;

        public LR0Builder(Grammar grammar, Builder.BuildMethod buildMethod)
        {
            if (buildMethod != Builder.BuildMethod.LR0 && buildMethod != Builder.BuildMethod.SLR)
                throw new ArgumentException();

            this.grammar = grammar;
            this.buildMethod = buildMethod;
        }
        
        public void BuildStatemachine()
        {
            //todo: der grammatik eine künstliche startregel hinzufügen
            Production prod = grammar.Productions[grammar.Start].First();
            
            graph = new Graph<HashSet<LR0Item>, Symbol>(
                        new SetEqualityComparer<LR0Item, LR0Item.LR0ItemEqualityComparer>(), 
                        new Symbol.SymbolEqualityComparer(),
                        new Func<HashSet<LR0Item>, string>(Tools.HashSetPrinter<LR0Item>));

            EndItem = LR0Item.CreateItem_DotAtEnd(prod);

            var lr0Cmp = new LR0Item.LR0ItemEqualityComparer();
            StartSet = new HashSet<LR0Item>(lr0Cmp);
            StartSet.Add(LR0Item.CreateItem_DotAtStart(prod));
            StartSet = LR0Item.Closure0(grammar, StartSet);

            Stack<HashSet<LR0Item>> OpenStack = new Stack<HashSet<LR0Item>>();
            
            OpenStack.Push(StartSet);

            while (OpenStack.Count > 0)
            {
                HashSet<LR0Item> working = OpenStack.Pop();

                if (graph.TryAddState(working))
                {
                    HashSet<Symbol> doneSet = new HashSet<Symbol>(new Symbol.SymbolEqualityComparer());
                    foreach (LR0Item item in working)
                    {
                        Symbol symbolOnRightSide = item.GetSymbolOnRightside();
                        if (symbolOnRightSide != null && !doneSet.Contains(symbolOnRightSide))
                        {
                            doneSet.Add(symbolOnRightSide);
                            HashSet<LR0Item> newState = LR0Item.Goto0(grammar, working, symbolOnRightSide);
                            graph.AddRelation(working, symbolOnRightSide, newState);
                            OpenStack.Push(newState);
                        }
                    }
                }
            }

            //Console.WriteLine(graph.ToString());
        }

        public ParseTable BuildTable()
        {
            if (graph == null || StartSet == null || EndItem == null)
                throw new Exception("Calculate Statemaschine first");

            ParseTable pt = new ParseTable();
            var lr0Cmp = new LR0Item.LR0ItemEqualityComparer();

            var stateMapper = graph.CreateStateMapper();
            pt.Start = stateMapper[StartSet];

            foreach (Pair<HashSet<LR0Item>, Symbol> pair in graph.GetPairEnumerator())
            {
                if (pair.Item2 is Terminal)
                    pt.AddShift(stateMapper[pair.Item1], pair.Item2, stateMapper[graph.FollowState(pair)]);
                else if (pair.Item2 is Nonterminal)
                    pt.AddJumpRule(stateMapper[pair.Item1], pair.Item2, stateMapper[graph.FollowState(pair)]);
            }

            foreach (var state in graph.GetStateEnumerator())
            {
                if (state.Contains(EndItem))
                    pt.AddAccept(stateMapper[state], Symbol.EOF);
            }

            FollowSets FollowSets = null;
            if (buildMethod == Builder.BuildMethod.SLR)
            {
                FollowSets = SetFactory.CreateFollowSets(grammar);
            }

            var Terminals = grammar.getTerminals();
            foreach (var set in graph.GetStateEnumerator())
                foreach (var item in set.Where(i => i.IsItemWithDotFarRight() && !lr0Cmp.Equals(i, EndItem)))
                {
                    if (buildMethod == Builder.BuildMethod.SLR)
                    {
                        var followSet = FollowSets[item.production.Head];
                        foreach (var terminal in Terminals.Where(term => followSet.Contains(term)))
                            pt.AddReduce(stateMapper[set], terminal, item.production); //todo: s/r imd r/r Konflikte abfangen -> passiert gdw. die grammatik nicht SLR ist.

                        if (followSet.Contains(Symbol.EOF))
                            pt.AddReduce(stateMapper[set], Symbol.EOF, item.production);
                    }
                    else
                    {
                        foreach (var terminal in Terminals)
                            pt.AddReduce(stateMapper[set], terminal, item.production); //todo: s/r imd r/r Konflikte abfangen -> passiert gdw. die grammatik nicht LR(0) ist.

                        pt.AddReduce(stateMapper[set], Symbol.EOF, item.production);
                    }
                }

            return pt;
        }
    }
}
